#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import allure
from utils import strings
from playwright.sync_api import expect
import re
import logging
from typing import Pattern
from dateutil.parser import parse


class PageShould:
    def __init__(self, draPage):
        from dragon.page import DraPage
        self.draPage: DraPage = draPage
        self._should = expect(self.draPage._page)

    def contain_title(self, title: str):
        stepText = f'断言：当前页面标题应该包含【{title}】'
        with allure.step(stepText):
            logging.info(stepText)
            title = title.replace('?', '\?')
            self._should.to_have_title(re.compile(f'{title}'))
        return self.draPage

    def contain_url(self, url: str):
        stepText = f'断言：当前页面url应该包含【{url}】'

        with allure.step(stepText):
            logging.info(stepText)
            url = url.replace('?', '\?')
            self._should.to_have_url(re.compile(f'{url}'))
        return self.draPage

    def have_title(self, title: str):
        stepText = f'断言：当前页面标题应该为【{title}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_have_title(title)
        return self.draPage

    def have_url(self, url: str):
        stepText = f'断言：当前页面url应该为【{url}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_have_url(url)
        return self.draPage

    def not_have_title(self, title: str):
        stepText = f'断言：当前页面标题应该不为【{title}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_have_title(title)
        return self.draPage

    def not_contain_title(self, title: str):
        stepText = f'断言：当前页面标题应该不包含【{title}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_have_title(re.compile(title))
        return self.draPage

    def not_have_url(self, url: str):
        stepText = f'断言：当前页面url应该不为【{url}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_have_url(url)
        return self.draPage

    def not_contain_url(self, url: str):
        stepText = f'断言：当前页面url应该不包含【{url}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_have_url(url)
        return self.draPage

    def reload_after_click(self, draLocator, timeout=1000):
        stepText = f'断言：点击【{draLocator._se.name}】后，页面应该刷新'
        ifReload = False

        def handler():
            nonlocal ifReload
            ifReload = True

        self.draPage.on_request(handler)
        draLocator.click()
        self.draPage.wait_for_timeout(timeout)
        with allure.step(stepText):
            logging.info(stepText)
            assert ifReload == True
        # with self.draPage._page.expect_request_finished(re.compile(relaodApi),timeout=20000):
        #     self.draPage.locator(se).click()
        return self.draPage

    def auto_reload_within(self, timeout: float):
        stepText = f'断言：页面应该自动刷新'
        ifReload = False

        def handler():
            nonlocal ifReload
            ifReload = True

        self.draPage.on_request(handler)
        self.draPage.wait_for_timeout(timeout)
        with allure.step(stepText):
            logging.info(stepText)
            assert ifReload == True
        return self.draPage

    def not_auto_reload_within(self, timeout: float):
        stepText = f'断言：页面应该没有自动刷新'
        ifReload = False

        def handler():
            nonlocal ifReload
            ifReload = True

        self.draPage.on_request(handler)
        self.draPage.wait_for_timeout(timeout)
        with allure.step(stepText):
            logging.info(stepText)
            assert ifReload == False
        return self.draPage


class LocatorShould:
    def __init__(self, draLocator):
        from dragon.locator import DraLocator
        self.draLocator: DraLocator = draLocator
        self._se = self.draLocator._se
        self._should = expect(self.draLocator._locator)
        if self.draLocator.count()>0:
            self.draLocator.wait_for()

    def be_checked(self):
        stepText = f'断言：【{self._se.name}】应该勾选'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_checked()
        return self.draLocator

    def not_be_checked(self):
        stepText = f'断言：【{self._se.name}】应该没勾选'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_be_checked()
        return self.draLocator

    def be_editable(self):
        stepText = f'断言：【{self._se.name}】应该可编辑'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_editable()
        return self.draLocator

    def not_be_editable(self):
        stepText = f'断言：【{self._se.name}】应该不可编辑'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_be_editable()
        return self.draLocator

    def be_enabled(self):
        stepText = f'断言：【{self._se.name}】应该可操作'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_enabled()
        return self.draLocator

    def not_be_enabled(self):
        stepText = f'断言：【{self._se.name}】应该不可操作'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_be_enabled()
        return self.draLocator

    def be_disabled(self):
        stepText = f'断言：【{self._se.name}】应该不可用'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_disabled()
        return self.draLocator

    def be_focused(self):
        stepText = f'断言：【{self._se.name}】应该聚焦'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_focused()
        return self.draLocator

    def not_be_focused(self):
        stepText = f'断言：【{self._se.name}】应该不聚焦'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_be_focused()
        return self.draLocator

    def be_visible(self):
        stepText = f'断言：【{self._se.name}】应该可见'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_visible()
        return self.draLocator

    def be_hidden(self):
        stepText = f'断言：【{self._se.name}】应该不可见'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_hidden()
        return self.draLocator

    def be_empty(self):
        '''元素没有text或输入框为空'''
        stepText = f'断言：【{self._se.name}】的值应该为空'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_empty()
        return self.draLocator

    def not_be_empty(self):
        stepText = f'断言：【{self._se.name}】的值应该不为空'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_be_empty()
        return self.draLocator

    def contain_text(self, expected: str | Pattern | list, use_inner_text: bool = False, ignore_case: bool = True):
        length = self.draLocator.count()
        if isinstance(expected, (str, Pattern)) and length > 1:
            expected = [expected] * length
        stepText = f'断言：【{self._se.name}】总共{length}个，且应该包含【{expected}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_contain_text(expected=expected, use_inner_text=use_inner_text, ignore_case=ignore_case)
        return self.draLocator

    def have_text(self, expected: str | Pattern | list, use_inner_text: bool = False, ignore_case: bool = True):
        length = self.draLocator.count()
        if isinstance(expected, (str, Pattern)) and length > 1:
            expected = [expected] * length
        stepText = f'断言：【{self._se.name}】总共{length}个，且应该等于【{expected}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_have_text(expected=expected, use_inner_text=use_inner_text, ignore_case=ignore_case)
        return self.draLocator

    def not_have_text(self, expected: str | Pattern | list, use_inner_text: bool = False, ignore_case: bool = True):
        length = self.draLocator.count()
        if isinstance(expected, (str, Pattern)) and length > 1:
            expected = [expected] * length
        stepText = f'断言：【{self._se.name}】总共{length}个，且应该不等于【{expected}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_have_text(expected=expected, use_inner_text=use_inner_text, ignore_case=ignore_case)
        return self.draLocator

    def not_contain_text(self, expected: str | Pattern | list, use_inner_text: bool = False, ignore_case: bool = True):
        length = self.draLocator.count()
        if isinstance(expected, (str, Pattern)) and length > 1:
            expected = [expected] * length
        stepText = f'断言：【{self._se.name}】总共{length}个，且应该不包含【{expected}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_have_text(expected=expected, use_inner_text=use_inner_text, ignore_case=ignore_case)
        return self.draLocator

    def disappear(self):
        stepText = f'断言：【{self._se.name}】应该消失'
        with allure.step(stepText):
            logging.info(stepText)
            self.draLocator.wait_for('hidden')

    def appear(self):
        stepText = f'断言：【{self._se.name}】应该出现'
        with allure.step(stepText):
            logging.info(stepText)
            self.draLocator.wait_for('visible')

    def be_in_viewport(self, ratio=0):
        stepText = f'断言：【{self._se.name}】应该可见'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_be_in_viewport(ratio=ratio)
        return self.draLocator

    def not_be_in_viewport(self, ratio=0):
        stepText = f'断言：【{self._se.name}】应该不可见'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_be_in_viewport(ratio=ratio)
        return self.draLocator

    def have_attr(self, attr: str, value: str | Pattern):
        stepText = f'断言：【{self._se.name}】应该拥有属性【{attr}】且等于【{value}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_have_attribute(attr, value)
        return self.draLocator

    def not_have_attr(self, attr: str, value: str | Pattern):
        stepText = f'断言：【{self._se.name}】应该不拥有属性【{attr}】或不等于【{value}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_to_have_attribute(attr, value)
        return self.draLocator

    def have_class(self, class_name: str | Pattern):
        stepText = f'断言：【{self._se.name}】应该拥有`class`属性且等于【{class_name}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_have_class(class_name)
        return self.draLocator

    def have_value(self, value: str | Pattern):
        stepText = f'断言：【{self._se.name}】的值应该等于【{value}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_have_value(value)
        return self.draLocator

    def not_have_value(self, value):
        stepText = f'断言：【{self._se.name}】的值应该不等于：【{value}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.not_have_value(value)
        return self.draLocator

    def have_location(self, location: dict):
        stepText = f'断言：【{self._se.name}】的位置应该为：【{location}】'
        with allure.step(stepText):
            logging.info(stepText)
            assert self.draLocator.location == location, f'【{self._se.name}】的位置【{self.draLocator.location}】!=【{location}】'
        return self.draLocator

    ###### locator匹配多个元素 ######
    def have_count(self, count):
        stepText = f'断言：【{self._se.name}】数量({self.draLocator.count()})应该为【{count}】'
        with allure.step(stepText):
            logging.info(stepText)
            self._should.to_have_count(count)
        return self.draLocator

    def have_count_greater_than(self, count: int):
        stepText = f'断言：【{self._se.name}】数量({self.draLocator.count()})应该大于【{count}】'
        with allure.step(stepText):
            logging.info(stepText)
            assert self.draLocator.count() > count, f'【{self._se.name}】数量({self.draLocator.count()})不大于【{count}】'
        return self.draLocator

    def have_count_less_than(self, count: int):
        stepText = f'断言：【{self._se.name}】数量({self.draLocator.count()})应该小于【{count}】'
        with allure.step(stepText):
            logging.info(stepText)
            assert self.draLocator.count() < count, f'【{self._se.name}】数量({self.draLocator.count()})不小于【{count}】'
        return self.draLocator

    def be_in_range(self, minValue: str, maxValue: str, dateTimeStr: bool = False):
        '''断言：所有值应该都在minValue和maxValue之间'''
        textList = self.draLocator.all_text()
        if dateTimeStr:
            extractor = strings.extract_date
            minValue = extractor(minValue)
            maxValue = extractor(maxValue)
        else:
            extractor = strings.extract_number
        results = list(map(extractor, textList))
        stepText = f'断言：【{self._se.name}】应该在【{minValue}~{maxValue}】之间'
        with allure.step(stepText):
            logging.info(stepText)
            assert max(results) <= maxValue, f'【{self._se.name}】不在【{minValue}~{maxValue}】之间'
            assert min(results) >= minValue, f'【{self._se.name}】不在【{minValue}~{maxValue}】之间'
        return self.draLocator

    def be_asc(self, dateTimeStr: bool = False):
        """
        断言：应该升序排列

        :param dateTimeStr: bool，是否是时间字符串
        :return: None
        """
        # self.have_count_greater_than(0)
        textList = self.draLocator.all_text()
        extractor = strings.extract_date if dateTimeStr else strings.extract_number
        results = list(map(extractor, textList))
        stepText = f'断言：【{self._se.name}】应该是升序排列'
        with allure.step(stepText):
            logging.info(stepText)
            # 应该升序排列
            assert results == sorted(results), f'【{self._se.name}】不是升序排序'
        return self.draLocator

    def be_desc(self, dateTimeStr=False):
        '''
        断言：应该降序排列

        :param dateTimeStr: bool，是否是时间字符串
        :return: None
        '''
        # self.have_count_greater_than(0)
        textList = self.draLocator.all_text()
        extractor = strings.extract_date if dateTimeStr else strings.extract_number
        results = list(map(extractor, textList))
        stepText = f'断言：【{self._se.name}】应该是降序排列'
        with allure.step(stepText):
            logging.info(stepText)
            # 应该降序排列
            assert results == sorted(results, reverse=True), f'【{self._se.name}】不是降序排序'
        return self.draLocator
